1 /* 2 * Collie - An asynchronous event-driven network framework using Dlang development 3 * 4 * Copyright (C) 2015-2017 Shanghai Putao Technology Co., Ltd 5 * 6 * Developer: putao's Dlang team 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 module collie.codec.http.httpmessage; 12 13 import collie.codec.http.headers; 14 import collie.codec.http.exception; 15 16 import std.typecons; 17 import std.typetuple; 18 import std.socket; 19 import std.variant; 20 import std.conv; 21 import std.exception; 22 import std.string; 23 24 alias HTTPMessage = HttpMessage; 25 26 enum HttpMessageType 27 { 28 unknown, 29 request, 30 response 31 } 32 33 /** 34 */ 35 class HttpMessage 36 { 37 private HttpMessageType _messageType; 38 this() 39 { 40 initilizeVersion(); 41 _messageType = HttpMessageType.unknown; 42 } 43 44 this(HttpMessageType messageType) 45 { 46 initilizeVersion(); 47 _messageType = messageType; 48 if(messageType == HttpMessageType.response) 49 _resreq.res = Response(); 50 else if(messageType == HttpMessageType.request) 51 _resreq.req = Request(); 52 } 53 54 private void initilizeVersion() 55 { 56 _version[0] = 1; 57 _version[1] = 1; 58 _versionStr = "1.1"; 59 } 60 61 /* Setter and getter for the SPDY priority value (0 - 7). When serialized 62 * to SPDY/2, Codecs will collpase 0,1 -> 0, 2,3 -> 1, etc. 63 * 64 * Negative values of pri are interpreted much like negative array 65 * indexes in python, so -1 will be the largest numerical priority 66 * value for this SPDY version (i.e. 3 for SPDY/2 or 7 for SPDY/3), 67 * -2 the second largest (i.e. 2 for SPDY/2 or 6 for SPDY/3). 68 */ 69 enum byte kMaxPriority = 7; 70 71 // static byte normalizePriority(byte pri) { 72 // if (pri > kMaxPriority || pri < -kMaxPriority) { 73 // // outside [-7, 7] => highest priority 74 // return kMaxPriority; 75 // } else if (pri < 0) { 76 // return pri + kMaxPriority + 1; 77 // } 78 // return pri; 79 // } 80 81 /** 82 * Is this a chunked message? (fpreq, fpresp) 83 */ 84 @property void chunked(bool chunked) 85 { 86 _chunked = chunked; 87 } 88 89 @property bool chunked() const 90 { 91 return _chunked; 92 } 93 94 /** 95 * Is this an upgraded message? (fpreq, fpresp) 96 */ 97 @property void upgraded(bool upgraded) 98 { 99 _upgraded = upgraded; 100 } 101 102 @property bool upgraded() const 103 { 104 return _upgraded; 105 } 106 107 /** 108 * Set/Get client address 109 */ 110 @property void clientAddress(Address addr) 111 { 112 request()._clientAddress = addr; 113 request()._clientIP = addr.toAddrString(); 114 request()._clientPort = addr.toPortString; 115 } 116 117 @property Address clientAddress() 118 { 119 return request()._clientAddress; 120 } 121 122 string getClientIP() 123 { 124 return request()._clientIP; 125 } 126 127 string getClientPort() 128 { 129 return request()._clientPort; 130 } 131 132 /** 133 * Set/Get destination (vip) address 134 */ 135 @property void dstAddress(Address addr) 136 { 137 _dstAddress = addr; 138 _dstIP = addr.toAddrString; 139 _dstPort = addr.toPortString; 140 } 141 142 @property Address dstAddress() 143 { 144 return _dstAddress; 145 } 146 147 string getDstIP() 148 { 149 return _dstIP; 150 } 151 152 string getDstPort() 153 { 154 return _dstPort; 155 } 156 157 /** 158 * Set/Get the local IP address 159 */ 160 @property void localIp(string ip) 161 { 162 _localIP = ip; 163 } 164 165 @property string localIp() 166 { 167 return _localIP; 168 } 169 170 @property void method(HTTPMethod method) 171 { 172 request()._method = method; 173 } 174 175 @property HTTPMethod method() 176 { 177 return request()._method; 178 } 179 //void setMethod(folly::StringPiece method); 180 181 string methodString() 182 { 183 return method_strings[request()._method]; 184 } 185 186 void setHTTPVersion(ubyte maj, ubyte min) 187 { 188 _version[0] = maj; 189 _version[1] = min; 190 _versionStr = format("%d.%d", maj, min); 191 } 192 193 auto getHTTPVersion() 194 { 195 Tuple!(ubyte, "maj", ubyte, "min") tv; 196 tv.maj = _version[0]; 197 tv.min = _version[1]; 198 return tv; 199 } 200 201 string getProtocolVersion() 202 { 203 return _versionStr; 204 } 205 206 @property void url(string url) 207 { 208 auto idx = url.indexOf('?'); 209 if (idx > 0) 210 { 211 request()._path = url[0 .. idx]; 212 request()._query = url[idx + 1 .. $]; 213 } 214 else 215 { 216 request()._path = url; 217 } 218 request()._url = url; 219 } 220 221 @property string url() 222 { 223 return request()._url; 224 } 225 226 @property wantsKeepAlive() 227 { 228 return _wantsKeepalive; 229 } 230 231 @property wantsKeepAlive(bool klive) 232 { 233 _wantsKeepalive = klive; 234 } 235 /** 236 * Access the path component (fpreq) 237 */ 238 string getPath() 239 { 240 return request()._path; 241 } 242 243 /** 244 * Access the query component (fpreq) 245 */ 246 string getQueryString() 247 { 248 return request()._query; 249 } 250 251 @property void statusMessage(string msg) 252 { 253 response()._statusMsg = msg; 254 } 255 256 @property string statusMessage() 257 { 258 return response()._statusMsg; 259 } 260 261 /** 262 * Access the status code (fpres) 263 */ 264 @property void statusCode(ushort status) 265 { 266 response()._status = status; 267 } 268 269 @property ushort statusCode() 270 { 271 return response()._status; 272 } 273 274 @property string host() 275 { 276 return _headers.getSingleOrEmpty(HTTPHeaderCode.HOST); 277 } 278 279 /** 280 * Access the headers (fpreq, fpres) 281 */ 282 ref HttpHeaders getHeaders() 283 { 284 return _headers; 285 } 286 287 void addHeader(HTTPHeaderCode code, string value) 288 { 289 _headers.add(code, value); 290 } 291 292 void addHeader(string name, string value) 293 { 294 _headers.add(name, value); 295 } 296 297 void setHeader(HTTPHeaderCode code, string value) 298 { 299 _headers.set(code, value); 300 } 301 302 void setHeader(string name, string value) 303 { 304 _headers.set(name, value); 305 } 306 307 /** 308 * Decrements Max-Forwards header, when present on OPTIONS or logDebug methods. 309 * 310 * Returns HTTP status code. 311 */ 312 int processMaxForwards() 313 { 314 auto m = method(); 315 if (m == HTTPMethod.HTTP_logDebug || m == HTTPMethod.HTTP_OPTIONS) 316 { 317 string value = _headers.getSingleOrEmpty(HTTPHeaderCode.MAX_FORWARDS); 318 if (value.length > 0) 319 { 320 long max_forwards = -1; 321 322 collectException(to!long(value), max_forwards); 323 324 if (max_forwards < 0) 325 { 326 return 400; 327 } 328 else if (max_forwards == 0) 329 { 330 return 501; 331 } 332 else 333 { 334 _headers.set(HTTPHeaderCode.MAX_FORWARDS, to!string(max_forwards - 1)); 335 } 336 } 337 } 338 return 0; 339 } 340 341 /** 342 * Returns true if the version of this message is HTTP/1.0 343 */ 344 bool isHTTP1_0() const 345 { 346 return _version[0] == 1 && _version[1] == 0; 347 } 348 349 /** 350 * Returns true if the version of this message is HTTP/1.1 351 */ 352 bool isHTTP1_1() const 353 { 354 return _version[0] == 1 && _version[1] == 1; 355 } 356 357 /** 358 * Returns true if this is a 1xx response. 359 */ 360 bool is1xxResponse() 361 { 362 return (statusCode() / 100) == 1; 363 } 364 365 /** 366 * Fill in the fields for a response message header that the server will 367 * send directly to the client. 368 * 369 * @param version HTTP version (major, minor) 370 * @param statusCode HTTP status code to respond with 371 * @param msg textual message to embed in "message" status field 372 * @param contentLength the length of the data to be written out through 373 * this message 374 */ 375 void constructDirectResponse(ubyte maj, ubyte min, const int statucode, 376 string statusMsg, int contentLength = 0) 377 { 378 statusCode(cast(ushort) statucode); 379 statusMessage(statusMsg); 380 constructDirectResponse(maj, min, contentLength); 381 } 382 383 /** 384 * Fill in the fields for a response message header that the server will 385 * send directly to the client. This function assumes the status code and 386 * status message have already been set on this HTTPMessage object 387 * 388 * @param version HTTP version (major, minor) 389 * @param contentLength the length of the data to be written out through 390 * this message 391 */ 392 void constructDirectResponse(ubyte maj, ubyte min, int contentLength = 0) 393 { 394 setHTTPVersion(maj, min); 395 _headers.set(HTTPHeaderCode.CONTENT_LENGTH, to!string(contentLength)); 396 if (!_headers.exists(HTTPHeaderCode.CONTENT_TYPE)) 397 { 398 _headers.add(HTTPHeaderCode.CONTENT_TYPE, "text/plain"); 399 } 400 chunked(false); 401 upgraded(false); 402 } 403 404 /** 405 * Check if query parameter with the specified name exists. 406 */ 407 bool hasQueryParam(string name) 408 { 409 parseQueryParams(); 410 return _queryParams.get(name, string.init) != string.init; 411 } 412 /** 413 * Get the query parameter with the specified name. 414 * 415 * Returns a reference to the query parameter value, or 416 * proxygen::empty_string if there is no parameter with the 417 * specified name. The returned value is only valid as long as this 418 * HTTPMessage object. 419 */ 420 string getQueryParam(string name, string defaults = string.init) 421 { 422 parseQueryParams(); 423 return _queryParams.get(name, defaults); 424 } 425 /** 426 * Get the query parameter with the specified name after percent decoding. 427 * 428 * Returns empty string if parameter is missing or folly::uriUnescape 429 * query param 430 */ 431 string getDecodedQueryParam(string name) 432 { 433 import std.uri; 434 435 parseQueryParams(); 436 string v = _queryParams.get(name, string.init); 437 if (v == string.init) 438 return v; 439 return decodeComponent(v); 440 } 441 442 /** 443 * Get the query parameter with the specified name after percent decoding. 444 * 445 * Returns empty string if parameter is missing or folly::uriUnescape 446 * query param 447 */ 448 string[string] queryParam() 449 { 450 parseQueryParams(); 451 return _queryParams; 452 } 453 454 void queryParam(string[string] v) 455 { 456 _queryParams = v; 457 } 458 459 /** 460 * Set the query string to the specified value, and recreate the url_. 461 * 462 */ 463 void setQueryString(string query) 464 { 465 unparseQueryParams(); 466 request._query = query; 467 } 468 /** 469 * Remove the query parameter with the specified name. 470 * 471 */ 472 void removeQueryParam(string name) 473 { 474 parseQueryParams(); 475 _queryParams.remove(name); 476 } 477 478 /** 479 * Sets the query parameter with the specified name to the specified value. 480 * 481 * Returns true if the query parameter was successfully set. 482 */ 483 void setQueryParam(string name, string value) 484 { 485 parseQueryParams(); 486 _queryParams[name] = value; 487 } 488 489 /** 490 * @returns true if this HTTPMessage represents an HTTP request 491 */ 492 bool isRequest() const 493 { 494 return _messageType == HttpMessageType.request; 495 } 496 497 /** 498 * @returns true if this HTTPMessage represents an HTTP response 499 */ 500 bool isResponse() const 501 { 502 return _messageType == HttpMessageType.response; 503 } 504 505 static string statusText(int code) 506 { 507 switch (code) 508 { 509 case 100: 510 return "Continue"; 511 case 101: 512 return "Switching Protocols"; 513 case 102: 514 return "Processing"; // RFC2518 515 case 103: 516 return "Early Hints"; 517 case 200: 518 return "OK"; 519 case 201: 520 return "Created"; 521 case 202: 522 return "Accepted"; 523 case 203: 524 return "Non-Authoritative logInformation"; 525 case 204: 526 return "No Content"; 527 case 205: 528 return "Reset Content"; 529 case 206: 530 return "Partial Content"; 531 case 207: 532 return "Multi-Status"; // RFC4918 533 case 208: 534 return "Already Reported"; // RFC5842 535 case 226: 536 return "IM Used"; // RFC3229 537 case 300: 538 return "Multiple Choices"; 539 case 301: 540 return "Moved Permanently"; 541 case 302: 542 return "Found"; 543 case 303: 544 return "See Other"; 545 case 304: 546 return "Not Modified"; 547 case 305: 548 return "Use Proxy"; 549 case 306: 550 return "Reserved"; 551 case 307: 552 return "Temporary Redirect"; 553 case 308: 554 return "Permanent Redirect"; // RFC7238 555 case 400: 556 return "Bad Request"; 557 case 401: 558 return "Unauthorized"; 559 case 402: 560 return "Payment Required"; 561 case 403: 562 return "Forbidden"; 563 case 404: 564 return "Not Found"; 565 case 405: 566 return "Method Not Allowed"; 567 case 406: 568 return "Not Acceptable"; 569 case 407: 570 return "Proxy Authentication Required"; 571 case 408: 572 return "Request Timeout"; 573 case 409: 574 return "Conflict"; 575 case 410: 576 return "Gone"; 577 case 411: 578 return "Length Required"; 579 case 412: 580 return "Precondition Failed"; 581 case 413: 582 return "Request Entity Too Large"; 583 case 414: 584 return "Request-URI Too Long"; 585 case 415: 586 return "Unsupported Media Type"; 587 case 416: 588 return "Requested Range Not Satisfiable"; 589 case 417: 590 return "Expectation Failed"; 591 case 418: 592 return "I'm a teapot"; // RFC2324 593 case 422: 594 return "Unprocessable Entity"; // RFC4918 595 case 423: 596 return "Locked"; // RFC4918 597 case 424: 598 return "Failed Dependency"; // RFC4918 599 case 425: 600 return "Reserved for WebDAV advanced collections expired proposal"; // RFC2817 601 case 426: 602 return "Upgrade Required"; // RFC2817 603 case 428: 604 return "Precondition Required"; // RFC6585 605 case 429: 606 return "Too Many Requests"; // RFC6585 607 case 431: 608 return "Request Header Fields Too Large"; // RFC6585 609 case 451: 610 return "Unavailable For Legal Reasons"; // RFC7725 611 case 500: 612 return "Internal Server Error"; 613 case 501: 614 return "Not Implemented"; 615 case 502: 616 return "Bad Gateway"; 617 case 503: 618 return "Service Unavailable"; 619 case 504: 620 return "Gateway Timeout"; 621 case 505: 622 return "HTTP Version Not Supported"; 623 case 506: 624 return "Variant Also Negotiates"; // RFC2295 625 case 507: 626 return "Insufficient Storage"; // RFC4918 627 case 508: 628 return "Loop Detected"; // RFC5842 629 case 510: 630 return "Not Extended"; // RFC2774 631 case 511: 632 return "Network Authentication Required"; // RFC6585 633 default: 634 if (code >= 600) 635 return "Unknown"; 636 if (code >= 500) 637 return "Unknown server error"; 638 if (code >= 400) 639 return "Unknown error"; 640 if (code >= 300) 641 return "Unknown redirection"; 642 if (code >= 200) 643 return "Unknown success"; 644 if (code >= 100) 645 return "Unknown information"; 646 return " "; 647 } 648 } 649 650 protected: 651 /** The 12 standard fields for HTTP messages. Use accessors. 652 * An HTTPMessage is either a Request or Response. 653 * Once an accessor for either is used, that fixes the type of HTTPMessage. 654 * If an access is then used for the other type, a DCHECK will fail. 655 */ 656 struct Request 657 { 658 Address _clientAddress; 659 string _clientIP; 660 string _clientPort; 661 HTTPMethod _method = HTTPMethod.HTTP_INVAILD; 662 string _path; 663 string _query; 664 string _url; 665 } 666 667 struct Response 668 { 669 ushort _status = 200; 670 string _statusStr; 671 string _statusMsg; 672 } 673 674 ref Request request() 675 { 676 // FIXME: Needing refactor or cleanup -@zxp at 5/24/2018, 1:09:52 PM 677 // 678 if (_messageType == HttpMessageType.unknown) 679 { 680 _messageType = HttpMessageType.request; 681 _resreq.req = Request(); 682 } 683 else if (_messageType == HttpMessageType.response) 684 { 685 throw new HTTPMessageTypeException("the message type is Response not Request"); 686 } 687 return _resreq.req; 688 } 689 690 ref Response response() 691 { 692 if (_messageType == HttpMessageType.unknown) 693 { 694 _messageType = HttpMessageType.response; 695 _resreq.res = Response(); 696 } 697 else if (_messageType == HttpMessageType.request) 698 // if (_messageType != HttpMessageType.response) 699 { 700 throw new HTTPMessageTypeException("the message type is Request not Response"); 701 } 702 703 return _resreq.res; 704 } 705 706 protected: 707 //void parseCookies(){} 708 709 void parseQueryParams() 710 { 711 import collie.utils.string; 712 713 if (_parsedQueryParams) 714 return; 715 _parsedQueryParams = true; 716 string query = getQueryString(); 717 if (query.length == 0) 718 return; 719 splitNameValue(query, '&', '=', (string name, string value) { 720 name = strip(name); 721 value = strip(value); 722 _queryParams[name] = value; 723 return true; 724 }); 725 } 726 727 void unparseQueryParams() 728 { 729 _queryParams.clear(); 730 _parsedQueryParams = false; 731 } 732 733 union Req_Res 734 { 735 Request req; 736 Response res; 737 } 738 739 private: 740 Address _dstAddress; 741 string _dstIP; 742 string _dstPort; 743 744 string _localIP; 745 string _versionStr; 746 Req_Res _resreq; 747 ubyte[2] _version; 748 HttpHeaders _headers; 749 string[string] _queryParams; 750 751 bool _parsedCookies = false; 752 bool _parsedQueryParams = false; 753 bool _chunked = false; 754 bool _upgraded = false; 755 bool _wantsKeepalive = true; 756 } 757 758 /** 759 */ 760 enum HttpStatusCodes 761 { 762 CONTINUE = 100, 763 SWITCHING_PROTOCOLS = 101, 764 PROCESSING = 102, // RFC2518 765 EARLY_HINTS = 103, // RFC8297 766 767 OK = 200, 768 CREATED = 201, 769 ACCEPTED = 202, 770 NON_AUTHORITATIVE_INFORMATION = 203, 771 NO_CONTENT = 204, 772 RESET_CONTENT = 205, 773 PARTIAL_CONTENT = 206, 774 MULI_STATUS = 207, 775 ALREADY_REPORTED = 208, // RFC5842 776 IM_USED = 226, // RFC3229 777 778 MULTIPLE_CHOICES = 300, 779 MOVED_PERMANENTLY = 301, 780 FOUND = 302, 781 SEE_OTHER = 303, 782 NOT_MODIFIED = 304, 783 USE_PROXY = 305, 784 TEMPORARY_REDIRECT = 307, 785 PERMANENTLY_REDIRECT = 308, // RFC7238 786 787 BAD_REQUEST = 400, 788 UNAUTHORIZED = 401, 789 PAYMENT_REQUIRED = 402, 790 FORHIDDEN = 403, 791 NOT_FOUND = 404, 792 METHOD_NOT_ALLOWED = 405, 793 NOT_ACCEPTABLE = 406, 794 PROXY_AUTHENTICATION_REQUIRED = 407, 795 REQUEST_TIMEOUT = 408, 796 CONFLICT = 409, 797 GONE = 410, 798 LENGTH_REQUIRED = 411, 799 PRECONDITION_FAILED = 412, 800 REQUEST_ENTITY_TOO_LARGE = 413, 801 REQUEST_URI_TOO_LARGE = 414, 802 UNSUPPORTED_MEDIA_TYPE = 415, 803 REQUESTED_RANGE_NOT_SATISFIABLE = 416, 804 EXPECTATION_FAILED = 417, 805 I_AM_A_TEAPOT = 418, 806 MISDIRECTED_REQUEST = 421, 807 UNPROCESSABLE_ENTITY = 422, 808 LOCKED = 423, 809 FAILED_DEPENDENCY = 424, 810 RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425, 811 UPGRADE_REQUIRED = 426, 812 PRECONDITION_REQUIRED = 428, // RFC6585 813 TOO_MANY_REQUESTS = 429, // RFC6585 814 REQUEST_HEADER_FIELDS_TOO_LARGE = 431, // RFC6585 815 UNAVAILABLE_FOR_LAGAL_REASONS = 451, 816 817 INTERNAL_SERVER_ERROR = 500, 818 NOT_IMPLEMENTED = 501, 819 BAD_GATEWAY = 502, 820 SERVICE_UNAVALIBALE = 503, 821 GATEWAY_TIMEOUT = 504, 822 VERSION_NOT_SUPPORTED = 505, 823 VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506, // RFC2295 824 INSUFFICIENT_STORAGE = 507, // RFC4918 825 LOOP_DETECTED = 508, // RFC5842 826 NOT_EXTENDED = 510, // RFC2774 827 NETWORK_AUTHENTICATION_REQUIRED = 511 // RFC6585 828 } 829 830 bool isSuccessCode(HttpStatusCodes code) 831 { 832 return (code >= 200 && code < 300); 833 }